///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_IMAGE_CIMAGE_H_
#define _BUOLA_IMAGE_CIMAGE_H_

#include <buola/image/ccolor.h>
#include <buola/image/cmview.h>
#include <buola/image/algorithm.h>
#include <buola/image/adaptors.h>
#include <buola/utility/cvirtualallocator.h>
#include <buola/datetime/clocks.h>
#include <map>

namespace buola { namespace img {

namespace detail {

    struct CImageBuffer
    {
        std::size_t mRefCount;
        CVirtualAllocator *mAlloc;
        uint8_t *mData;
        CCalendarTime mTimeStamp;
        std::map<std::string,void*> *mMetaData;
    };

/*namespace detail*/ }

template<typename tFmt>
class CImage : public CMView<tFmt>
{
    typedef CMView<tFmt> TBase;

public:
    CImage()
        :   mBuffer(nullptr)
    {}

    explicit CImage(const CSize_i &pSize,CVirtualAllocator *pAlloc,std::size_t pAlign=0)
    {
        CreateBuffer(pSize,CalcStrideForAlign(pSize.x,pAlign),pAlloc);
    }

    explicit CImage(CVirtualAllocator *pAlloc)
        :   CImage({0,0},pAlloc)
    {}

    explicit CImage(const CSize_i &pSize,CVirtualAllocator *pAlloc,const CPixel<tFmt> &pPixel,std::size_t pAlign=0)
        :   CImage(pSize,pAlloc,pAlign)
    {
        fill_pixels(*this,pPixel);
    }

    explicit CImage(const CSize_i &pSize,std::size_t pAlign=0)
        :   CImage(pSize,nullptr,pAlign)
    {}

    explicit CImage(const CSize_i &pSize,const CPixel<tFmt> &pPixel,std::size_t pAlign=0)
        :   CImage(pSize,nullptr,pPixel,pAlign)
    {}

    CImage(NClone,const CImage &pRH,NSameAlloc)
    {
        if(pRH.mData)
        {
            CreateBuffer(pRH.mSize,pRH.mStride,pRH.mBuffer->mAlloc);
            copy_pixels(pRH,*this);
        }
        else
        {
            mBuffer=nullptr;
        }
    }

    CImage(NClone,const CView<tFmt> &pRH,CVirtualAllocator *pAlloc=nullptr)
    {
        CreateBuffer(pRH.Size(),pRH.Stride(),pAlloc);
        copy_pixels(pRH,*this);
    }

    CImage(NShare,const CImage &pRH)
        :   TBase(pRH)
        ,   mBuffer(pRH.mBuffer)
    {
        if(mBuffer)
            ++mBuffer->mRefCount;
    }

    CImage(const CImage &pRH)=delete;

    CImage(CImage &&pRH)
        :   TBase(pRH)
        ,   mBuffer(pRH.mBuffer)
    {
        pRH.mBuffer=nullptr;
        pRH.mSize.x=0;
    }

    CImage(NSteal,const CSize_i &pSize,uint8_t *pBuffer,ptrdiff_t pStride,CVirtualAllocator *pAlloc=nullptr)
        :   mBuffer(nullptr)
    {
        StealData(pSize,pBuffer,pStride,pAlloc);
    }

    explicit CImage(CAnyImage&&);

    ~CImage()
    {
        if(mBuffer)
            DropBuffer();
    }

    bool Null() const                       {   return this->Width()==0;    }

    CVirtualAllocator *Allocator() const     {   return mBuffer?mBuffer->mAlloc:nullptr;        }
    const CCalendarTime &TimeStamp() const  {   return mBuffer->mTimeStamp;     }
    CCalendarTime &TimeStamp()              {   return mBuffer->mTimeStamp;     }
    void TimeStampNow()             {   mBuffer->mTimeStamp=CCalendarClock::now(); }

    void Recreate(const CSize_i &pSize,CVirtualAllocator *pAlloc,std::size_t pAlign=0)
    {
        ptrdiff_t lStride=CalcStrideForAlign(pSize.x,pAlign);

        if(mBuffer)
            RecreateBuffer(pSize,lStride,pAlloc);
        else
            CreateBuffer(pSize,lStride,pAlloc);
    }

    void Recreate(const CSize_i &pSize,CVirtualAllocator *pAlloc,const CPixel<tFmt> &pPixel,std::size_t pAlign=0)
    {
        Recreate(pSize,pAlloc,pAlign);

        fill_pixels(*this,pPixel);
    }

    void Recreate(const CSize_i &pSize,std::size_t pAlign=0)
    {
        ptrdiff_t lStride=CalcStrideForAlign(pSize.x,pAlign);

        if(mBuffer)
            RecreateBuffer(pSize,lStride,mBuffer->mAlloc);
        else
            CreateBuffer(pSize,lStride,nullptr);
    }

    void Recreate(const CSize_i &pSize,const CPixel<tFmt> &pPixel,std::size_t pAlign=0)
    {
        Recreate(pSize,pAlign);

        fill_pixels(*this,pPixel);
    }

    void StealData(const CSize_i &pSize,uint8_t *pBuffer,ptrdiff_t pStride,CVirtualAllocator *pAlloc=nullptr)
    {
        if(mBuffer)
            DropBuffer();

        mBuffer=new detail::CImageBuffer;
        mBuffer->mRefCount=1;
        mBuffer->mAlloc=pAlloc;
        mBuffer->mData=pBuffer;
        TBase::mData=pBuffer;
        TBase::mSize=pSize;
        TBase::mStride=pStride;
    }

    void Reallocate(CVirtualAllocator *pAlloc)
    {
        if(!mBuffer) return;
        if(mBuffer->mAlloc==pAlloc) return;

        CImage lImage(nClone,*this,pAlloc);

        std::swap(mBuffer,lImage.mBuffer);
    }

    CImage &operator=(const CImage &pRH)=delete;

    CImage &operator=(CImage &&pRH)
    {
        if(pRH.mBuffer!=mBuffer)
            std::swap(mBuffer,pRH.mBuffer);
        TBase::operator=(pRH);
        pRH.mSize.x=0;
        return *this;
    }

private:
    void CreateBuffer(const CSize_i &pSize,ptrdiff_t pStride,CVirtualAllocator *pAlloc)
    {
        mBuffer=new detail::CImageBuffer;
        mBuffer->mRefCount=1;
        mBuffer->mAlloc=pAlloc;
        if(pAlloc)
            mBuffer->mData=(uint8_t*)pAlloc->Allocate(pSize.y*pStride);
        else
            mBuffer->mData=(uint8_t*)alloc_mem(pSize.y*pStride);

        TBase::mData=mBuffer->mData;
        TBase::mSize=pSize;
        TBase::mStride=pStride;
    }

    void DropBuffer()
    {
        //TODO:use some kind of locking
        if(--mBuffer->mRefCount==0)
        {
            if(mBuffer->mAlloc)
                mBuffer->mAlloc->Free(mBuffer->mData);
            else
                free(mBuffer->mData);
            delete mBuffer;
        }

        mBuffer=nullptr;
    }

    void RecreateBuffer(const CSize_i &pSize,ptrdiff_t pStride,CVirtualAllocator *pAlloc)
    {
        if(mBuffer->mRefCount>1)
        {
            DropBuffer();
            CreateBuffer(pSize,pStride,pAlloc);
            return;
        }

        if(TBase::mSize==pSize&&pStride==TBase::mStride&&pAlloc==mBuffer->mAlloc)
            return;

        if(mBuffer->mAlloc)
            mBuffer->mAlloc->Free(mBuffer->mData);
        else
            free(mBuffer->mData);
        mBuffer->mAlloc=pAlloc;
        if(pAlloc)
            mBuffer->mData=(uint8_t*)pAlloc->Allocate(pSize.y*pStride);
        else
            mBuffer->mData=(uint8_t*)alloc_mem(pSize.y*pStride);

        TBase::mData=mBuffer->mData;
        TBase::mSize=pSize;
        TBase::mStride=pStride;
    }

    static ptrdiff_t CalcStrideForAlign(int pWidth,std::size_t pAlign)
    {
        return pAlign?round_up_to_multiple(sizeof(typename TBase::TValue)*TBase::cChannels*pWidth,pAlign):
                                       (sizeof(typename TBase::TValue)*TBase::cChannels*pWidth);
    }

private:
    detail::CImageBuffer *mBuffer;

    friend class CAnyImage;
};

template<typename tFormat>
CImage<tFormat> clone(const CView<tFormat> &pImg)
{
    return CImage<tFormat>(nClone,pImg);
}

template<typename tFmt>
struct MIsImage<CImage<tFmt>>
{
    static const bool value=true;
};

template<typename tFmt>
struct MIsView<CImage<tFmt>>
{
    static const bool value=true;
};

template<typename tNewFormat,typename tOldFormat>
CImage<tNewFormat> convert(const CView<tOldFormat> &pImg)
{
    CImage<tNewFormat> lImg(pImg.Size());
    convert_pixels(pImg,lImg);
    return lImg;
}

/*namespace img*/ } /*namespace buola*/ }

#endif
